package br.ufpe.cin.msc.jrsj2.recommender.algorithm.cf;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import br.ufpe.cin.msc.jrsj2.recommender.algorithm.RecommendationAlgorithm;
import br.ufpe.cin.msc.jrsj2.recommender.persistence.dao.DAOFactory;
import br.ufpe.cin.msc.jrsj2.recommender.persistence.dao.ProfileDAO;
import br.ufpe.cin.msc.jrsj2.recommender.persistence.domain.Client;
import br.ufpe.cin.msc.jrsj2.recommender.persistence.domain.Profile;
import br.ufpe.cin.msc.jrsj2.recommender.persistence.domain.Program;

public class CorrelationBased extends RecommendationAlgorithm implements
		CollaborativeFiltering {

	private ProfileDAO profileDAO;
	private float activeProfileAverage;

	public CorrelationBased(Client activeClient, List<Program> itens,
			List<Profile> activeProfileList) {
		super(activeClient, itens, activeProfileList);
		profileDAO = DAOFactory.getDAOFactory().getProfileDAO();
	}

	@Override
	public List<Program> getRecommendations(int size) {
		List<Program> recommendations = new ArrayList<Program>();

		if (activeProfileList.isEmpty())
			return recommendations;

		Map<Profile, Float> neighborhood = new HashMap<Profile, Float>();
		List<Profile> profiles = profileDAO.loadAllUnless(activeClient);
		Map<Client, Float> clients = new HashMap<Client, Float>();

		for (Profile profile : profiles) {
			if (neighborhood.size() == size)
				break;
			for (Program item : itens)
				if (item.getTitle().equals(profile.getProgram().toString())) {
					Float correlationResult = clients.get(profile.getClient());
					if (correlationResult == null) {
						correlationResult = pearsonCorrelation(profile
								.getClient());
						clients.put(profile.getClient(), correlationResult);
					}
					if (correlationResult > 0)
						neighborhood.put(profile, correlationResult);
					break;
				}
		}

		if (!neighborhood.isEmpty()) {
			List<Entry<Profile, Float>> list = new ArrayList<Entry<Profile, Float>>(
					neighborhood.entrySet());

			Collections.sort(list, new Comparator<Entry<Profile, Float>>() {
				@Override
				public int compare(Entry<Profile, Float> o1,
						Entry<Profile, Float> o2) {
					return o1.getValue().compareTo(o2.getValue());
				}
			});

			for (Entry<Profile, Float> entry : list)
				if (!recommendations.contains(entry.getKey().getProgram()))
					recommendations.add(entry.getKey().getProgram());
		}

		return recommendations;
	}

	private float pearsonRatingsAverage(Client client, List<Profile> profileList) {
		float activeClientSum = 0;
		float clientSum = 0;
		int quotient = 0;

		for (Profile activeProfile : activeProfileList)
			for (Profile profile : profileList)
				if (activeProfile.getProgram() == profile.getProgram()) {
					activeClientSum += activeProfile.getRating();
					clientSum += profile.getRating();
					quotient++;
					break;
				}

		if (quotient == 0) {
			activeProfileAverage = 0;
			return 0;
		}

		activeProfileAverage = activeClientSum / quotient;
		return clientSum / quotient;
	}

	private float pearsonCorrelation(Client client) {
		List<Profile> profileList = profileDAO.findProfileByClient(client);
		float profileAverage = pearsonRatingsAverage(client, profileList);
		float numerator = 0;
		float denominator1 = 0;
		float denominator2 = 0;

		for (Profile activeProfile : activeProfileList)
			for (Profile profile : profileList)
				if (activeProfile.getProgram() == profile.getProgram()) {
					float activeProfileRating = activeProfile.getRating();
					float profileRating = profile.getRating();
					float factor1 = activeProfileRating - activeProfileAverage;
					float factor2 = profileRating - profileAverage;

					numerator += factor1 * factor2;
					denominator1 += Math.pow(factor1, 2);
					denominator2 += Math.pow(factor2, 2);
					break;
				}
		if (numerator == 0)
			return 0;
		return (float) (numerator / Math.sqrt(denominator1 * denominator2));
	}

	/*
	 * private float ratingsAverage(Client client, Program program) {
	 * List<Profile> profileList = profileDAO.findProfileByClient(client); float
	 * quotient = 0; float sum = 0;
	 * 
	 * for (Profile profile : profileList) if (profile.getProgram() == program)
	 * { sum += profile.getRating(); quotient++; }
	 * 
	 * return quotient == 0 ? 0 : sum / quotient; }
	 */

	/*
	 * private float calculatePrediction(Program program) { List<Profile>
	 * profiles = profileDAO.loadAllUnless(activeClient); float denominator = 0;
	 * float numerator = 0;
	 * 
	 * for (Profile profile : profiles) { float clientPearsonCorrelation = Math
	 * .abs(pearsonCorrelation(profile.getClient())); denominator +=
	 * clientPearsonCorrelation; if (profile.getProgram() == program) {
	 * numerator += (profile.getRating() - ratingsAverage( profile.getClient(),
	 * program)) clientPearsonCorrelation; } }
	 * 
	 * return activeProfileAverage + numerator / denominator; }
	 */

}
